[contents] [prev] [top] (7 out of 7)

Defining Class and Instance Variables

Previous sections in this chapter described a simple form for defining class and instance variables. This section contains further details on how to define class and instance variable in specific ways, including

Setters, Getters, and Real and Virtual Variables

The ScriptX language provides the following forms for accessing or changing class or instance variables:

classOrObject.variableName
classOrObject
.variableName := value

In contrast with many other object-oriented programming languages, these forms for querying and changing instance variables do not read or write the variable directly. Instead, they call special generic functions called setters (to change the variable) and getters (to query a variable's value), which then operate on the class or object to retrieve or change the value of that variable.

The use of setters and getters to access a class or object's variables allows a great deal of flexibility in querying or changing that variable.

This last feature, using setters and getters to mimic the existence of a real variable, is known as creating virtual class or instance variables. Virtual variables are different from real variables, which occupy real locations in memory (and take up space in the final object). Both real and virtual variables appear the same from the outside, and both are accessed in the same way. The only difference lies in their definition and storage within your class or object.

Defining Real Variables

To define real class or instance variables you use the class variables or instance variables sections of the class or object definitions, as described earlier in this chapter on page 113 and again on page 119.

class variableName ( classList ) 
class variables
varName
varName:initialValue
. . .
instance variables
varName
varName:initialValue
. . .
end
In addition to these simple forms where you specify either the name of the variable or the name and its initial value, each variable definition can also specify a set of optional keywords to specify, primarily, how this variable is to be handled when this class or object is stored in the ScriptX object store. Those keywords appear in a specific order before the variable name and its initial value:

[ readOnly ] [ transient [ initializer function  ]  ] \
[ reference ] varName [ :initialValue ]
The qualifiers to each variable are all optional, but they must be specified in the order shown for them to be processed correctly.

The first optional qualifier, readOnly, indicates that this variable can be retrieved but cannot be changed. When this variable is created, only a getter method is constructed for it.

The remaining qualifiers are used for classes and objects that are stored into ScriptX storage containers, part of the ScriptX object store. By default, when a class or object is stored, all of its class and instance variables are also stored with their current values. Additionally, when the class or object is loaded from the storage container, all the objects that its instance variables hold are also retrieved.

The remaining three optional qualifiers allow you to specialize how each variable is to behave when stored:

The transient qualifier specifies that when the class or object is stored, the value of this variable is discarded. Transient variables have initial values of undefined when they are restored, unless an initializer function is specified.

The initializer qualifier specifies that when a transient object is restored, the function specified by function is executed to set the initial value of this instance variable. (Only transient variables can take an initializer.) The function expression can hold any of the following expressions, where appropriate:

The reference qualifier specifies that when the class or object this variable belongs to is restored, the object this variable holds is not restored along with it. Instead, that object is only restored when the variable is queried. The reference qualifier is useful for variables that hold very large objects that do not immediately need to be loaded into memory when your ScriptX program starts running.

For further details on the ScriptX object store and how objects are stored, see the ScriptX Components Guide.

Defining Virtual Class or Instance Variables

Virtual class or instance variables, as mentioned in the introduction to this section, are variables that occupy no slots in memory and take up no space in the object. Virtual variables do not actually exist; instead, their values are calculated by setter and getter functions when the variable is accessed or queried, and their values returned as if the variable did actually occupy a real location in memory. In this way, virtual variables mimic the appearance of real class or instance variables.

Virtual class or instance variables are particularly useful for variables that may frequently change as a consequence of some other operation on that object. Take, for example, an object that holds other objects such as a collection. That collection object might have a size instance variable that returns the number of elements in that collection. Any operation that adds or removes elements from that array would have to change the value of that instance variable, requiring an extra step (and more time) for each add or removal operation.

If size is a virtual instance variable, the size would never be stored anywhere in memory. Instead, when the size instance variable is queried, the getter method counts the items in the array and returns that value. No memory is used for that number, and each method that adds or removes elements from the array has one less operation it has to keep track of.

When you define a real class instance variable, ScriptX automatically creates a setter and getter method for that variable when the class or object is created. To define virtual instance variables, you must define setter and getter methods for those variables yourself.

ScriptX has two forms for defining setter and getter methods (two for creating setter methods, and two for creating getter methods):

method variableSetter self value -> body
method variableGetter self -> body
method set variable self value -> body
method get variable self -> body
In each definition, variable is the name of the class or instance variable. In the first two definitions, the variable name appears just before the words Setter or Getter (with no space in between). For example, the getter method for the virtual instance variable x would be the xGetter method. In the second two definitions, the variable name appears after the reserved words get or set.

Each of the setter and getter forms are equivalent; that is, the method set form is equivalent to the variableSetter form and the method get form is equivalent to variableGetter.

Getter methods always have only one argument, self, and setters have two, self and the value the variable is to be "set" to. The body part of each method should always return the value of the virtual instance variable as its last argument.

If the virtual instance variable is to be considered read-only, define only a getter method for that variable and not a setter method.

The names of virtual instance variables should not be defined in a class variables or instance variables definition; remember, as virtual instance variables, simply the existence of the setter and getter methods gives those variables the appearance of reality.

The following examples show how to define setter and getter methods within a class or object definition. The first example is trivial. It simply says that the instance variable ten has a value of 10.

method tenGetter self -> return 10
method tenSetter self value -> (
	print "Cannot change ten. ten is 10."
	10
)

The second example creates a virtual instance variable called position, which could be used to query or change the object's position in some two-dimensional space. Here position is built from the existing x and y instance variables. Setting position, an array of two elements, is accomplished by setting the values of x and y.

method get position self -> (
	return #(self.x, self.y)
)
method set position self value -> (
self.x := value[1]
self.y := value[2]
return value
)

The third example defines a virtual instance variable lineWidth that provides direct access to an instance variable that is defined by a member object.

method lineWidthGetter self -> (
	return self.stroke.lineWidth
)

The lineWidthGetter method could be defined as a free method for subclasses of certain core classes, such as TextPresenter. (Note that you cannot change a core method in a core class, but you can change a method in a subclass of a core class.)

-- create a subclass of TextPresenter and defind lineWidthGetter as a 
-- free method on it
class MyTextPresenter (TextPresenter) end

method lineWidthGetter self {class MyTextPresenter} -> (
	return self.stroke.lineWidth
)
-- now create a test of lineWidthGetter
global myTP := new MyTextPresenter \
	stroke:blackBrush fill:whiteBrush \
	boundary:(new Rect x2:200 y2:200) target:"inconceivable"
myTP.lineWidth
1

Specializing Setters and Getters for Real Variables

As mentioned previously, real variables are also queried and changed through the use of setter and getter methods. By default, a pair of setter and getter generic functions are constructed by ScriptX for each class or instance variable when you create a class or object with the class or object expressions. You can, however, augment or change the behavior of those setters and getters for real variables, just as you did for virtual variables (for example, to print debugging messages or to change the value of some other part of the class or object).

You can either specialize the setter and getter for a variable that has been defined in this same class or object, or you can specialize the setter and getter for a variable that has been defined in a superclass and inherited by this class or object. To specialize setters and getters, you use the same method definition syntax that you used to define virtual instance variables, as described on page 143.

Specializing Setters and Getters for Inherited Variables

You can override or augment the setter and getter behavior in your class for a variable defined in a superclass in much the same way that you override other methods in other classes. You define a method of the same name in your class, and you call nextMethod to pass the method call up the class hierarchy to the appropriate implementation of that method.

For example, suppose you have defined a class called VerbosePresenter that inherits from the class TwoDPresenter. The TwoDPresenter class defines x and y instance variables to indicate the position of your presenter in a coordinate space. In VerbosePresenter, you want to augment the behavior of the setter methods for x and y to print a message to the debugging stream each time x or y is changed. You also want to accept floating point values and store them for precision, but round them to the nearest integer value for actual display.

To do this, store the values with higher precision in another set of instance variables which you define. Define setter methods for x and y to transform the value from floating point to integer, print a message, and then call nextMethod, which allows the superclass to actually set the variable:

class VerbosePresenter (TwoDPresenter)
instance variables
_x, _y
	instance methods
method xSetter self value -> (
self._x := value
local val
format debug "x is stored internally as %* " value @normal
format debug "but displayed as %*\n" \
(val := round value) @normal
nextMethod self val
)
	method ySetter self value -> (
self._y := value
local val
format debug "y is stored internally as %* " value @normal
format debug "but displayed as %*\n" \
(val := round value) @normal
nextMethod self val
)
end

As previously noted, you should avoid setting any instance variables which are owned by superclasses before calling nextMethod. In the example above, the instance variables which are set (_x and _y) are not owned by any superclasses because they are defined in the new class VerbosePresenter. Therefore, in this particular case, there is no problem with calling nextMethod last.

If you do not specify nextMethod as the last expression in the setter or getter method body, you need to explicitly return the value of the variable as the last expression.

method xGetter self {class VerbosePresenter} -> (
	local value := nextMethod self
	format debug "x is displayed as %* " value @normal
	format debug "but stored internally as %*\n" self._x @normal
	return value
)
method yGetter self {class VerbosePresenter} -> (
	local value := nextMethod self
	format debug "y is displayed as %* " value @normal
	format debug "but stored internally as %*\n" self._y @normal
	return value
)

Specializing Setters and Getters for Variables Defined In the Same Class

In the current version of ScriptX, you cannot truly override the default setter and getter methods for a real class or instance variable defined in that same class or object. The default setter and getter methods, as defined when you create a real variable in a class or object expression, provide a way of bypassing the normal setter and getter mechanism and directly gaining access to the memory location that stores the value of that variable. When you override the default setter or getter behavior for a variable, you lose the ability to directly access the variable. For example, to change the default behavior of a getter method for the variable x, you would have to use the expression self.x in the body of that getter method, which calls the getter method for x that you just defined, which queries self.x, and so on.

You can mimic the effect of augmenting the default setter and getter behavior by implementing your variable as a virtual variable, and then using a "placeholder" variable to hold the actual value of the variable whose behavior you are augmenting.

For example, suppose your class Person has an instance variable called name which can only have values of the class String or of one of its subclasses. Because of this restriction on the possible values of name, you need to define a nameSetter method that checks the class of the value to which the variable is set to make sure that it is a string, and reports an error otherwise.

Because you cannot change the default behavior of a real variable defined on this class, you create a placeholder for that real variable, for example, a real variable called _name. Then implement your nameSetter method, and in the body of the method definition, use the placeholder variable _name instead of the variable name to hold the actual name value:

class Person ()
instance variables _name
instance methods
method nameSetter self value -> (
if ((isaKindOf value String) == false) then
print "bad value for IV name."
else self._name := value
)
end

Now, if you instantiate the class Person, you can use name just as if it were a real variable, and nameSetter checks to make sure the object you are assigning to name is of the right class.

foo := new Person
foo.name := 43
"bad value for IV name"
foo.name := "Elsa"
"Elsa"

This next example uses a fake placeholder variable (_radius) for the virtual variable radius in the class MyRoundClass:

class MyRoundClass ()
instance variables _radius
instance methods
method set radius self value -> (
	format debug "changing radius to %*" value @normal
	self._radius := value
)
method get radius self -> (
	format debug "The value of radius is %*" self._radius @normal
	return self._radius
)
end


This document is part of the ScriptX Language Guide, one of the volumes of the ScriptX Technical Reference Series. ScriptX is developed by the ScriptX Engineering Team at Apple Computer, successor to the Kaleida Engineering Team at Kaleida Labs, Inc.

Copyright 1996 Apple Computer, Inc. All Rights Reserved.